package com.mapforge.map;

import java.awt.image.BufferedImage;
import java.util.List;

import com.mapforge.exceptions.LessThanOneDimensionException;
import com.mapforge.util.ImageUtil;

/**
 * This class represents a set of tiles.
 * 
 * @author William Morrison
 * 
 */
public class Tileset {
	/**
	 * The image's this tiles in this tileset use
	 */
	private BufferedImage[] images;

	/**
	 * The relative path to the image which represents this tileset
	 */
	private String imagePath;

	/**
	 * Frequently used selections of tiles
	 */
	private List<TileStamp> stampSelections;

	/**
	 * All tiles within this tileset.
	 */
	private Tile[] tiles;

	/** Tile width of tiles in this tileset */
	private int tileWidth;
	/**
	 * Tile height of tiles in this tileset
	 */
	private int tileHeight;

	/**
	 * The empty tile in this Tileset. Even images with transparent pixels take
	 * time to render. A tile with transparent pixels will not be rendered.
	 */
	private int emptyTileIndex;

	/**
	 * The name of this Tileset.
	 */
	private String name;

	/**
	 * Width, in pixels, of this tileset's image.
	 */
	private int tilesetImageWidth;
	/**
	 * Height, in pixels, of this tileset's image.
	 */
	private int tilesetImageHeight;

	/**
	 * Constructs a new tileset with the specified name, and tile dimensions. A
	 * {@link Tileset} constructed in this way has no real use until it has a
	 * {@link BufferedImage} specified, as without an image a tileset has no
	 * {@link Tile}.
	 * 
	 * @param name
	 *            name for this tileset
	 * @param tileWidth
	 *            width of tiles in this tileset
	 * @param tileHeight
	 *            height of tiles in this tileset
	 */
	public Tileset(String name, int tileWidth, int tileHeight) {
		this(name, null, tileWidth, tileHeight);
	}

	/**
	 * Constructs a new {@link Tileset} with the specified name, image, and tile
	 * dimensions.
	 * 
	 * @param name
	 *            name for this tileset
	 * @param image
	 *            image to use for this tileset's tiles
	 * @param tileWidth
	 *            width of tiles in this tileset
	 * @param tileHeight
	 *            height of tiles in this tileset
	 * @throws LessThanOneDimensionException
	 *             if either tile width or height is less than one
	 * @throws IllegalArgumentException
	 *             if either tile width or height is less than the actual
	 *             dimensions of a tile's image
	 */
	public Tileset(String name, BufferedImage image, int tileWidth,
			int tileHeight) throws LessThanOneDimensionException,
			IllegalArgumentException {
		if (tileWidth < 1)
			throw new LessThanOneDimensionException(
					"Tile width cannot be less than 0.");
		else if (tileHeight < 1)
			throw new LessThanOneDimensionException(
					"Tile height cannot be less than 0.");

		this.name = name;
		this.tileWidth = tileWidth;
		this.tileHeight = tileHeight;
		emptyTileIndex = 0;
		if (image != null) {
			this.images = ImageUtil.splitImages(image, tileWidth, tileHeight);
			tiles = new Tile[images.length];
			for (int i = 0; i < images.length; i++) {
				tiles[i] = new Tile(images[i], this, i);
			}
			tilesetImageWidth = image.getWidth();
			tilesetImageHeight = image.getHeight();
		} else {
			tilesetImageWidth = tilesetImageHeight = 0;
			tiles = new Tile[0];
			images = new BufferedImage[0];
		}
	}

	/**
	 * Returns the tiles which make up this tileset
	 * 
	 * @return
	 */
	public Tile[] getTiles() {
		return tiles;
	}

	/**
	 * 
	 * @return
	 */
	public BufferedImage[] getImages() {
		return images;
	}

	/**
	 * 
	 * @param image
	 * @param tileWidth
	 * @param tileHeight
	 */
	public void setImages(BufferedImage image, int tileWidth, int tileHeight) {
		this.images = ImageUtil.splitImages(image, tileWidth, tileHeight);
		tiles = new Tile[images.length];
		for (int i = 0; i < images.length; i++) {
			tiles[i] = new Tile(images[i], this, i);
		}
		this.tileWidth = tileWidth;
		this.tileHeight = tileHeight;
		this.tilesetImageWidth = image.getWidth();
		this.tilesetImageHeight = image.getHeight();
	}

	/**
	 * 
	 * @return
	 */
	public String getImagePath() {
		return imagePath;
	}

	public Tile getTileAt(int x, int y) {
		y *= this.getColumns();
		y += x;
		if (y > tiles.length) {
			throw new ArrayIndexOutOfBoundsException("Tile at " + x + " " + y
					+ " exceeds number of known tiles");
		}
		return tiles[y];
	}

	/**
	 * 
	 * @param imagePath
	 */
	public void setImagePath(String imagePath) {
		this.imagePath = imagePath;
	}

	/**
	 * 
	 * @param tileIndex
	 * @param dir
	 * @return
	 */
	public boolean isTilePassable(int tileIndex, Direction dir) {
		if (tileIndex < tiles.length && tileIndex > -1)
			return tiles[tileIndex].isPassable(dir);
		else
			throw new IndexOutOfBoundsException(
					"While checking index for passable tiles in \""
							+ this.getName() + "\", 0 <= " + tileIndex + " <= "
							+ tiles.length);
	}

	/**
	 * Returns the name of this tileset
	 * 
	 * @return name of this tileset
	 * @see #setName(String)
	 */
	public String getName() {
		return name;
	}

	/**
	 * Sets the name of this tileset
	 * 
	 * @param name
	 *            name of this tileset
	 * @see #getName()
	 */
	public void setName(String name) {
		this.name = name;
	}

	/**
	 * 
	 * @return
	 */
	public List<TileStamp> getTileStamps() {
		return stampSelections;
	}

	/**
	 * 
	 * @param tileStamp
	 */
	public void addTileStamp(TileStamp tileStamp) {
		stampSelections.add(tileStamp);
	}

	/**
	 * 
	 * @param index
	 * @return
	 */
	public Tile getTile(int index) {
		if (index < tiles.length && index > -1)
			return tiles[index];
		else
			throw new IndexOutOfBoundsException(
					"Index Out Of Bounds while retrieving tile from \""
							+ this.getName() + "\" (not true that (-1 > "
							+ index + " < " + tiles.length + ")");
	}

	public int getRows() {
		return tilesetImageHeight / tileHeight;
	}

	public int getColumns() {
		return tilesetImageWidth / tileWidth;
	}

	public Tile getEmptyTile() {
		return tiles[emptyTileIndex];
	}

	/**
	 * 
	 * @return
	 */
	public int getEmptyTileIndex() {
		return emptyTileIndex;
	}

	/**
	 * Sets the width of tiles used in this tileset.
	 * 
	 * @param tileW
	 */
	public void setTileWidth(int tileW) {
		this.tileWidth = tileW;
	}

	public void setTileHeight(int tileH) {
		this.tileHeight = tileH;
	}

	public void setTileDimensions(int tileW, int tileH) {
		this.tileWidth = tileW;
		this.tileHeight = tileH;
	}

	public int getTileWidth() {
		return tileWidth;
	}

	public int getTileHeight() {
		return tileHeight;
	}

	public int getTilesetImageWidth() {
		return tilesetImageWidth;
	}

	public int getTilesetImageHeight() {
		return tilesetImageHeight;
	}

	/** */
	public void setEmptyTileIndex(int emptyIndex) {
		if (emptyIndex < tiles.length && emptyIndex > -1)
			this.emptyTileIndex = emptyIndex;
		throw new IndexOutOfBoundsException(
				"Index Out Of Bounds while retrieving tile from \""
						+ this.getName() + "\", 0 <= " + emptyIndex + " <= "
						+ tiles.length);
	}
}
